package ru.sstu.renju.config;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import ru.sstu.renju.core.Player;
import ru.sstu.renju.ui.GameRenderer;

/**
 * {@code ConfigReader} class is used to read configuration from XML file.
 *
 * @author denis_murashev
 * @since Renju 1.1
 */
public class ConfigReader {

	private static final String NAMESPACE_RENJU = "http://tfi.sstu.ru/renju";

	/**
	 * Reads configuration for given platform.
	 *
	 * @param platform platform
	 * @return configuration
	 */
	public Config getConfig(Platform platform) {
		Document document = getDocument();
		validate(document);
		Config config = new Config();
		config.setGameSize(getNumber(document,
				"/tfi:Game/tfi:GameSize").intValue());
		config.setRowSize(getNumber(document,
				"/tfi:Game/tfi:RowSize").intValue());
		config.setMoveDelay(getNumber(document,
				"/tfi:Game/tfi:Delay").longValue());
		readPlayers(document, config);
		config.setCellSize(getNumber(document,
				"/tfi:Game/tfi:CellSize").intValue());
		readRenderers(platform, document, config);
		return config;
	}

	private Document getDocument() {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			return builder.parse(getClass().getResourceAsStream("/renju.xml"));
		} catch (ParserConfigurationException e) {
			throw new RuntimeException(e);
		} catch (SAXException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private void validate(Document document) {
		try {
			SchemaFactory schemaFactory = SchemaFactory
					.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
			Schema schema = schemaFactory.newSchema(new StreamSource(getClass()
					.getResourceAsStream("/renju.xsd")));
			Validator validator = schema.newValidator();
			validator.validate(new DOMSource(document));
		} catch (SAXException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private void readPlayers(Document document, Config config) {
		try {
			XPath xPath = getXPath();
			String expression = "/tfi:Game/tfi:Players/tfi:Player/text()";
			NodeList list;
			list = (NodeList) xPath.evaluate(expression, document,
					XPathConstants.NODESET);
			List<Player> players = new ArrayList<Player>(list.getLength());
			for (int i = 0; i < list.getLength(); i++) {
				String name = list.item(i).getTextContent();
				Class<?> type = Class.forName(name);
				if (Player.class.isAssignableFrom(type)) {
					players.add((Player) type.newInstance());
				}
			}
			config.setPlayers(players);
			config.setBlackPlayer(players.get(0));
			config.setWhitePlayer(players.get(0));
		} catch (XPathExpressionException e) {
			throw new RuntimeException(e);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	private void readRenderers(Platform platform, Document document,
			Config config) {
		try {
			XPath xPath = getXPath();
			String expression = "/tfi:Game/tfi:Renderers/tfi:Renderer/text()";
			NodeList list = (NodeList) xPath.evaluate(expression, document,
					XPathConstants.NODESET);
			List<GameRenderer> renderers
					= new ArrayList<GameRenderer>(list.getLength());
			for (int i = 0; i < list.getLength(); i++) {
				String name = list.item(i).getTextContent();
				Class<?> type = Class.forName(name);
				if (platform.getRenderer().isAssignableFrom(type)) {
					GameRenderer renderer = (GameRenderer) type.newInstance();
					renderer.setConfig(config);
					renderers.add(renderer);
				}
			}
			config.setRenderers(renderers);
			config.setRenderer(renderers.get(0));
		} catch (XPathExpressionException e) {
			throw new RuntimeException(e);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	private Number getNumber(Document document, String expression) {
		try {
			return (Number) getXPath().evaluate(expression, document,
					XPathConstants.NUMBER);
		} catch (XPathExpressionException e) {
			throw new RuntimeException(e);
		}
	}

	private XPath getXPath() {
		XPathFactory xPathFactory = XPathFactory.newInstance();
		XPath xPath = xPathFactory.newXPath();
		xPath.setNamespaceContext(new RenjuNamespaceContext());
		return xPath;
	}

	private final class RenjuNamespaceContext implements NamespaceContext {
		@Override
		public Iterator<?> getPrefixes(String namespaceURI) {
			throw new UnsupportedOperationException();
		}

		@Override
		public String getPrefix(String namespaceURI) {
			throw new UnsupportedOperationException();
		}

		@Override
		public String getNamespaceURI(String prefix) {
			if (prefix == null) {
				return NAMESPACE_RENJU;
			} else if ("tfi".equals(prefix)) {
				return NAMESPACE_RENJU;
			} else if ("xml".equals(prefix)) {
				return XMLConstants.XML_NS_URI;
			}
			return XMLConstants.NULL_NS_URI;
		}
	}
}
